package com.jy.rhin.config.ws;

import lombok.extern.slf4j.Slf4j;
import org.apache.cxf.Bus;
import org.apache.cxf.jaxws.EndpointImpl;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.jws.WebService;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream;

/**
 * @Description: ws服务端工厂
 * @Author: lww
 * @Date: 2022/5/18
 */
@Slf4j
@Component
public class WsServiceFactory extends BaseWsFactory implements ApplicationListener<ApplicationReadyEvent> {

    @Resource
    private Bus bus;
    @Resource
    private WsServiceProperties wsServiceProperties;
    @Resource
    WsIPProperties wsIPProperties;
    /**
     * 所有发布ws服务的bean, 防止重复注册EndPoint，
     * key: beanName
     * value: serviceName
     */
    private final Map<String, Object> wsBeans = new HashMap<>();

    /**
     * 找到所有的 {@link WebService} 注解类的bean，做为ws服务端发布接口
     *
     * @param event the event to respond to
     */
    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        Map<String, Object> webServiceList = applicationContext.getBeansWithAnnotation(WebService.class);
        Iterator<Map.Entry<String, Object>> values = webServiceList.entrySet().iterator();
        while (values.hasNext()) {
            Map.Entry<String, Object> next = values.next();
            String beanName = next.getKey();
            if (wsBeans.containsKey(beanName)) {
                //如果bean已经注册了EndPoint，则忽略，不需要重复注册
                continue;
            }
            Object bean = next.getValue();
            Class<?> serverClass = AopUtils.isAopProxy(bean) || AopUtils.isJdkDynamicProxy(bean)
                    ? AopUtils.getTargetClass(bean)
                    : bean.getClass();
            String serviceName = getServiceName(beanName, bean, serverClass);
            publishEndpoint(beanName, bean, serviceName);
        }
    }

    /**
     * 获取WebService接口的 {@link WebService} 注解的name，做为服务名，后续发布Endpoint以此为url
     *
     * @param beanName
     * @param bean
     * @param serverClass
     * @return
     */
    private String getServiceName(String beanName, Object bean, Class<?> serverClass) {
        Class<?>[] serverInterfaces = serverClass.getInterfaces();
        Optional<Class<?>> optional = Stream.of(serverInterfaces).filter(clazz -> clazz.isAnnotationPresent(WebService.class)).findFirst();
        if (!optional.isPresent()) {
            throw new RuntimeException(String.format("bean %s, class %s 找不到WebService接口", beanName, bean.getClass().getName()));
        }
        Class<?> serverInterface = optional.get();
        WebService webService = serverInterface.getAnnotation(WebService.class);
        return webService.name();
    }

    /**
     * 开始声明endpoint并注册到spring上下文
     *
     * @param beanName
     * @param bean
     * @param serviceName
     */
    private void publishEndpoint(String beanName, Object bean, String serviceName) {
        AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
        EndpointImpl endpoint = new EndpointImpl(bus, bean);
        String serName = "/" + serviceName;
        String wsUrl = wsIPProperties.getIpStr() + serviceName;
        endpoint.setPublishedEndpointUrl(wsUrl);
        endpoint.publish(serName);
        endpoint.getInInterceptors().addAll(matchInterceptor(wsServiceProperties.getInInterceptors(), wsUrl));
        endpoint.getOutInterceptors().addAll(matchInterceptor(wsServiceProperties.getOutInterceptors(), wsUrl));
        ((DefaultListableBeanFactory) beanFactory).registerSingleton(serviceName, endpoint);
        log.info("创建WebService: {}, 发布服务{}", beanName, serviceName);
        wsBeans.put(beanName, serviceName);
    }

}
